package com.study.demo.aop.demo1_2.operation;

import org.springframework.aop.support.AopUtils;
import org.springframework.core.MethodClassKey;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: elvin
 * @time: 2019-10-16 14:45
 * @desc:
 **/
public abstract class AbstractFallbackLogOperationSource implements LogOperationSource {
    private static final Collection<LogOperation> NULL_LOGGING_ATTRIBUTE = new ArrayList<>();

    private final Map<Object, Collection<LogOperation>> attributeCache = new ConcurrentHashMap<>(1024);

    //获取方法中, 加入缓存, 提升效率, 不用每次都去解析
    @Override
    public LogOperation getLogOperations(Method method, Class<?> targetClass) {
        if (method.getDeclaringClass() == Object.class) {
            return null;
        }
        //根据方法和目标类, 计算出一个key值
        Object cacheKey = getCacheKey(method, targetClass);
        Collection<LogOperation> cached = this.attributeCache.get(cacheKey);
        if (!CollectionUtils.isEmpty(cached)) {
            return (cached != NULL_LOGGING_ATTRIBUTE ? cached.iterator().next() : null);
        }
        else{
            LogOperation logOps = computeLogOperations(method, targetClass);
            if(logOps != null){
                this.attributeCache.put(cacheKey, Arrays.asList(logOps));
            }
            else{
                this.attributeCache.put(cacheKey, NULL_LOGGING_ATTRIBUTE);
            }
            return logOps;
        }
    }


    protected Object getCacheKey(Method method, @Nullable Class<?> targetClass) {
        return new MethodClassKey(method, targetClass);
    }

    private LogOperation computeLogOperations(Method method, Class<?> targetClass){
        if(allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())){
            return null;
        }

        //通过代理对象上的方法, 找到真实对象上的方法, 是一个溯源的过程
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
        // First try is the method in the target class.
        LogOperation opDef = findLogOperations(specificMethod);
        if(opDef != null){
            return opDef;
        }

        // Second try is the logging operation on the target class.
        opDef = findLogOperations(specificMethod.getDeclaringClass());
        if(opDef != null && ClassUtils.isUserLevelMethod(method)){
            return opDef;
        }

        if(specificMethod != method){
            // Fallback is to look at the original method.
            opDef = findLogOperations(method);
            if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
                return opDef;
            }
        }
        return null;
    }


    protected boolean allowPublicMethodsOnly() {
        return false;
    }

    @Nullable
    protected abstract LogOperation findLogOperations(Method method);

    protected abstract LogOperation findLogOperations(Class<?> clazz);
}
